#ifndef QCLOGGER_H
#define QCLOGGER_H

#include "qccommon.h"
#include "qcfileappender.h"
#include <QAtomicInteger>
#include <QMutex>
#include <QDateTime>
#include <QThread>
#include <QFileInfo>
#include <QCoreApplication>
#include <QSharedPointer>
#include <QDebug>

#ifndef QCLOG_COLOR_UNIX_STYLE
#include <windows.h>
#endif

namespace QcLog {

class QcLogger
{
public:
    inline explicit QcLogger()
    {
#ifndef QCLOG_COLOR_UNIX_STYLE
        out_console_handle_ = GetStdHandle(STD_OUTPUT_HANDLE);
#endif
        file_appender_ = QSharedPointer<QcFileAppender>(new QcFileAppender);
        file_appender_->set_file_pathname(file_pathname_);
    }

    template<typename T>
    inline void info(const source_loc &loc, const T &msg)
    {
        log(Level::INFO, loc, basic_raw_message<T>(msg));
    }

    template<typename T>
    inline void debug(const source_loc &loc, const T &msg)
    {
        log(Level::DEBUG, loc, basic_raw_message<T>(msg));
    }

    template<typename T>
    inline void warning(const source_loc &loc, const T &msg)
    {
        log(Level::WARNING, loc, basic_raw_message<T>(msg));
    }

    template<typename T>
    inline void critical(const source_loc &loc, const T &msg)
    {
        log(Level::CRITICAL, loc, basic_raw_message<T>(msg));
    }

    template<typename T>
    inline void fatal(const source_loc &loc, const T &msg)
    {
        log(Level::FATAL, loc, basic_raw_message<T>(msg));
    }

    inline void set_console_level(Level log_level)
    {
        level_console_.store(log_level);
    }

    inline static Level default_console_level()
    {
        return Level::DEBUG;
    }

    inline void set_file_level(Level file_level)
    {
        level_file_.store(file_level);
    }

    inline static Level default_file_level()
    {
        return Level::DEBUG;
    }

    inline void set_console_pattern(const QString &pattern)
    {
        pattern_console_ = pattern;
    }

    inline static QString default_console_pattern()
    {
        return "[%{yyyy}-%{MM}-%{dd} %{hh}:%{mm}:%{ss}:%{zzz}][%{t}][%{l}][%{f}-%{r}-%{n}]: %{d}";
    }

    inline void set_file_pattern(const QString &pattern)
    {
        pattern_file_ = pattern;
    }

    inline static QString default_file_pattern()
    {
        return "[%{yyyy}-%{MM}-%{dd} %{hh}:%{mm}:%{ss}:%{zzz}][%{t}][%{l}][%{f}-%{r}-%{n}]: %{d}";
    }

    inline void set_file_pathname(const QString &pathname)
    {
        file_pathname_ = pathname;
    }

    inline static QString default_file_pathname()
    {
        return QCoreApplication::applicationDirPath()+"/log.txt";
    }

    inline void set_file_activation(const bool &activation)
    {
        file_activation_ = activation;
        file_appender_->set_activation(file_activation_);
    }

    inline bool default_file_activation()
    {
        return  true;
    }

protected:
    template<typename T>
    inline void log(Level lvl, source_loc loc, const basic_raw_message<T> msg)
    {
        QMutexLocker lock(&log_mutex_);
        if(!should_log(lvl))
        {
            return;
        }

        if(file_activation_ && should_file(lvl))
        {
            QString file_msg = format_file_msg(lvl, loc, msg.data());
            file_appender_->add_message(file_msg);
        }

        QString first_process_msg = format_console_msg(loc, msg.data());
        QString lvl_console_str;

#ifdef QCLOG_COLOR_UNIX_STYLE
        switch (lvl)
        {
            case Level::INFO :
                lvl_console_str = "\033[0;32;1m"+QCLOG_LEVEL_STRINGLIST[Level::INFO]+"\033[0m";//green
                break;
            case Level::DEBUG :
                lvl_console_str = "\033[0;33;1m"+QCLOG_LEVEL_STRINGLIST[Level::DEBUG]+"\033[0m";//yellow
                break;
            case Level::WARNING :
                lvl_console_str = "\033[0;36;1m"+QCLOG_LEVEL_STRINGLIST[Level::WARNING]+"\033[0m";//cyan
                break;
            case Level::CRITICAL :
                lvl_console_str = "\033[0;35;1m"+QCLOG_LEVEL_STRINGLIST[Level::CRITICAL]+"\033[0m";//magenta
                break;
            case Level::FATAL :
                lvl_console_str = "\033[0;31;1m"+QCLOG_LEVEL_STRINGLIST[Level::FATAL]+"\033[0m";//red
                break;
        }

        QTextStream out(stdout);
        if(!first_process_msg.contains("%{l}"))
        {
            out << first_process_msg << '\n';
        }
        else
        {
            first_process_msg.replace("%{l}", lvl_console_str);
            if(first_process_msg.at(first_process_msg.size()-1) != '\n') first_process_msg.append('\n');
            out << first_process_msg;
        }
#else
        QStringList msg_str_list = first_process_msg.split("%{l}");
        if(msg_str_list.size() <= 1)
        {
            ::SetConsoleTextAttribute(out_console_handle_, 7);
            auto size = static_cast<DWORD>(first_process_msg.size());
            ::WriteConsoleA(out_console_handle_, first_process_msg.toLocal8Bit().data(), size, nullptr, nullptr);
        }
        else
        {
            for (int i = 0; i < msg_str_list.size(); i++)
            {
                auto size = static_cast<DWORD>(msg_str_list.at(i).size());
                ::SetConsoleTextAttribute(out_console_handle_, 7);
                ::WriteConsoleA(out_console_handle_, msg_str_list.at(i).toLocal8Bit().data(), size, nullptr, nullptr);

                if( i != (msg_str_list.size()-1))
                {
                    switch (lvl)
                    {
                        case Level::INFO :
                            lvl_console_str = QCLOG_LEVEL_STRINGLIST[Level::INFO];
                            ::SetConsoleTextAttribute(out_console_handle_, 10);//green
                            break;
                        case Level::DEBUG :
                            lvl_console_str = QCLOG_LEVEL_STRINGLIST[Level::DEBUG];
                            ::SetConsoleTextAttribute(out_console_handle_, 14);//yellow
                            break;
                        case Level::WARNING :
                            lvl_console_str = QCLOG_LEVEL_STRINGLIST[Level::WARNING];
                            ::SetConsoleTextAttribute(out_console_handle_, 11);//cyan
                            break;
                        case Level::CRITICAL :
                            lvl_console_str = QCLOG_LEVEL_STRINGLIST[Level::CRITICAL];
                            ::SetConsoleTextAttribute(out_console_handle_, 13);//magenta
                            break;
                        case Level::FATAL :
                            lvl_console_str = QCLOG_LEVEL_STRINGLIST[Level::FATAL];
                            ::SetConsoleTextAttribute(out_console_handle_, 12);//red
                            break;
                    }
                    auto size_lvl = static_cast<DWORD>(lvl_console_str.size());
                    ::WriteConsoleA(out_console_handle_, lvl_console_str.toLocal8Bit().data(), size_lvl, nullptr, nullptr);
                }
            }
            ::SetConsoleTextAttribute(out_console_handle_, 7);
        }
        ::WriteConsoleA(out_console_handle_, QString("\n").toLocal8Bit().data(), 1, nullptr, nullptr);
#endif
    }

    inline bool should_log(Level msg_level) const
    {
        return msg_level >= level_console_.load();
    }

    inline bool should_file(Level msg_level) const
    {
        return msg_level >= level_file_.load();
    }

    inline QString format_console_msg(source_loc loc, const QString &msg)
    {
        QString final_msg;
        if(!pattern_console_.isEmpty())
        {
            final_msg = pattern_console_;
            QDateTime curDataTime = QDateTime::currentDateTime();
            final_msg.replace("%{yyyy}", curDataTime.toString("yyyy"));
            final_msg.replace("%{MM}", curDataTime.toString("MM"));
            final_msg.replace("%{dd}", curDataTime.toString("dd"));
            final_msg.replace("%{hh}", curDataTime.toString("hh"));
            final_msg.replace("%{mm}", curDataTime.toString("mm"));
            final_msg.replace("%{ss}", curDataTime.toString("ss"));
            final_msg.replace("%{zzz}", curDataTime.toString("zzz"));
            final_msg.replace("%{t}", "0x" + QString::number(qlonglong(QThread::currentThread()->currentThread()), 16));
            final_msg.replace("%{f}", QFileInfo(QString(loc.filename)).fileName());
            final_msg.replace("%{r}", QString::number(loc.line));
            final_msg.replace("%{n}", QString(loc.funcname));
            final_msg.replace("%{d}", msg);
        }
        return final_msg;
    }

    inline QString format_file_msg(Level lvl, source_loc loc, const QString &msg)
    {
        QString final_msg;
        if(!pattern_file_.isEmpty())
        {
            final_msg = pattern_file_;
            QDateTime curDataTime = QDateTime::currentDateTime();
            final_msg.replace("%{yyyy}", curDataTime.toString("yyyy"));
            final_msg.replace("%{MM}", curDataTime.toString("MM"));
            final_msg.replace("%{dd}", curDataTime.toString("dd"));
            final_msg.replace("%{hh}", curDataTime.toString("hh"));
            final_msg.replace("%{mm}", curDataTime.toString("mm"));
            final_msg.replace("%{ss}", curDataTime.toString("ss"));
            final_msg.replace("%{zzz}", curDataTime.toString("zzz"));
            final_msg.replace("%{t}", "0x" + QString::number(qlonglong(QThread::currentThread()->currentThread()), 16));
            final_msg.replace("%{l}", QCLOG_LEVEL_STRINGLIST[lvl]);
            final_msg.replace("%{f}", QFileInfo(QString(loc.filename)).fileName());
            final_msg.replace("%{r}", QString::number(loc.line));
            final_msg.replace("%{n}", QString(loc.funcname));
            final_msg.replace("%{d}", msg);
        }
        return final_msg;
    }

private:
#ifndef QCLOG_COLOR_UNIX_STYLE
    HANDLE      out_console_handle_;
#endif
    QAtomicInt                      level_console_{default_console_level()};
    QAtomicInt                      level_file_{default_file_level()};
    QMutex                          log_mutex_;
    QString                         pattern_console_{default_console_pattern()};
    QString                         pattern_file_{default_file_pattern()};
    QString                         file_pathname_{default_file_pathname()};
    bool                            file_activation_{default_file_activation()};
    QSharedPointer<QcFileAppender>  file_appender_;

};

} //namespace QcLog

#endif // QCLOGGER_H
